<template>
  <div class="code-mirror-wrapper" :style="{ border: border ? '1px solid #DCDFE6 !important' : 'none' }">
    <textarea ref="edit" v-model="value" :placeholder="placeholder" class="code-mirror-textarea"></textarea>
  </div>
</template>

<script>

export default {
  name: 'codeEditor',
  props: {
    value: {
      type: [Number, String]
    },
    option: {},
    variableKey: {
      type: String || Array,
      default: ''
    },
    variable: {
      type: Array,
      default: () => [
        {
          label: '默认变量',
          value: 'variable'
        }
      ]
    },
    placeholder: {
      type: String,
      default: ''
    },
    isInitCode: {
      type: Boolean,
      default: false
    },
    border: {
      type: Boolean,
      default: true
    },
    readOnly: {
      type: Boolean,
      default: false
    },
    code: {
      type: String,
      default: 'application/json'
    }//默认脚本编码  application/json  application/xml
  },
  data() {
    return {
      editor: null
    }
  },
  methods: {
    onChange(instance, event) {
      let value = this.editor.getValue()
      this.$emit('input', value)

      /*this.$emit('select', {
        value: value,
        selectValue: this.editor.getSelection()
      })*/

      /**
       * getValue():获取编辑器文本内容

       setValue(text):设置编辑器文本内容

       getRange({line,ch},{line,ch}):获取指定范围内的文本内容第一个对象是起始坐标，第二个是结束坐标

       replaceRange(replaceStr,{line,ch},{line,ch}):替换指定区域的内容

       getLine(line)：获取指定行的文本内容

       lineCount():统计编辑器内容行数

       firstLine():获取第一行行数，默认为0，从开始计数

       lastLine():获取最后一行行数

       getLineHandle(line):根据行号获取行句柄

       getSelection():获取鼠标选中区域的代码

       replaceSelection(str):替换选中区域的代码

       setSelection({line:num,ch:num1},{line:num2,ch:num3}):设置一个区域被选中

       somethingSelected()：判断是否被选择

       getEditor()：获取CodeMirror对像

       undo()：撤销

       redo():回退

       setCursor():设置光标位置
       */
    },
    onInput(instance, event) {
      let value = this.editor.getValue()
      !this.isInitCode && this.$emit('input', value)
    },
    initEditor() {
      let dom = this.$refs.edit,
        option = {
          lineNumbers: false,//是否显示行号
          mode: { name: this.code },//默认脚本编码  application/json  application/xml
          lineWrapping: true, //是否强制换行
          smartIndent: true,  // 是否智能缩进
          theme: 'customize-dark',//主题
          readOnly: this.readOnly,
          cursorHeight: 0.85,
          extraKeys: {
            '\'a\'': this.completeAfter,
            '\'b\'': this.completeAfter,
            '\'c\'': this.completeAfter,
            '\'d\'': this.completeAfter,
            '\'e\'': this.completeAfter,
            '\'f\'': this.completeAfter,
            '\'g\'': this.completeAfter,
            '\'h\'': this.completeAfter,
            '\'i\'': this.completeAfter,
            '\'j\'': this.completeAfter,
            '\'k\'': this.completeAfter,
            '\'l\'': this.completeAfter,
            '\'m\'': this.completeAfter,
            '\'n\'': this.completeAfter,
            '\'o\'': this.completeAfter,
            '\'p\'': this.completeAfter,
            '\'q\'': this.completeAfter,
            '\'r\'': this.completeAfter,
            '\'s\'': this.completeAfter,
            '\'t\'': this.completeAfter,
            '\'u\'': this.completeAfter,
            '\'v\'': this.completeAfter,
            '\'w\'': this.completeAfter,
            '\'x\'': this.completeAfter,
            '\'y\'': this.completeAfter,
            '\'z\'': this.completeAfter,
            '\'&\'': this.variableCompleteAfter
          }
        }

      if (this.option && this.option.extraKeys) {
        this.option.extraKeys = { ...option.extraKeys, ...this.option.extraKeys }
      }
      this.editor = CodeMirror.fromTextArea(dom, { ...option, ...this.option })
      this.editor.on('blur', function (instance, event) {
        this.onChange(instance, event)
      }.bind(this))
      this.editor.on('change', function (instance, event) {
        this.onInput(instance, event)
      }.bind(this))
      this.editor.on('cursorActivity', (instance, event) => {//光标位置改变事件
        let cursorIdx = instance.doc.sel.ranges[0].anchor.ch
        this.$emit('cursorChange', cursorIdx)
      })

      this.format()

    },
    completeAfter(cm, pred) {
      if (!pred || pred()) {
        setTimeout(function () {
          if (!cm.state.completionActive) {
            cm.showHint({
              completeSingle: false
            })
          }
        }, 100)
      }
      return CodeMirror.Pass
    },
    variableCompleteAfter(cm, pred) {
      let data = this.variable.map((v) => {
        return {
          text: String(`{${v.value}}`),
          displayText: `${v.label}:${v.value}`
        }
      })
      let options = {
        hint: cm => {
          let list = data, cursor = cm.getCursor(), currentLine = cm.getLine(cursor.line), start = cursor.ch,
            end = start

          while (end < currentLine && currentLine.length && /[\w|\u4e00-\u9fa5$]+/.test(currentLine.charAt(end))) ++end
          while (start && /[\w|\u4e00-\u9fa5$]+/.test(currentLine.charAt(start - 1))) --start
          let curWord = start !== end && currentLine.slice(start, end)
          let regex = new RegExp('^[a-zA-Z0-9_\u4e00-\u9fa5]{4}：' + curWord, 'i')

          return {
            from: CodeMirror.Pos(cursor.line, start),
            to: CodeMirror.Pos(cursor.line, end),
            list: (!curWord ? list : list.filter(item => item.displayText.match(regex))).sort()
          }
        },
        completeSingle: false
      }
      if (!pred || pred()) {
        setTimeout(function () {
          if (!cm.state.completionActive) {
            cm.showHint(options)
          }
        }, 100)
      }
      return CodeMirror.Pass
    },
    insert(v) {
      this.editor.replaceSelection(v)
      // XXX 获取不到光标坐标
      // this.editor.replaceRange(v, {line: 1,ch:1})
    },
    format() {// 格式化代码
      let totalLines = this.editor.lineCount()
      this.editor.autoFormatRange({ line: 0, ch: 0 }, { line: totalLines })
      this.editor.setCursor({ line: 0, ch: 0 })
    },
    setBookmark(node) {
      this.editor.setBookmark(this.editor.getCursor(), {
        widget: node,
        insertLeft: false,
        shared: false,
        handleMouseEvents: false
      })
    },
    markText(from, to) {
      this.editor.markText(from, to, {
        className: 'widget',
        inclusiveLeft: false,
        inclusiveRight: false,
        atomic: true
      })
    }
  },
  mounted() {
    this.initEditor()
  },
  watch: {
    'value': function (val) {
      // 获取当前光标位置
      let cursor = this.editor.getCursor()

      this.editor.setValue(val)
      // this.editor.execCommand('goDocEnd')

      // 还原光标位置
      this.editor.setCursor(cursor)
    }
  }
}

</script>

<style lang="scss" scoped>
.code-mirror-wrapper {
  width: calc(100% - 2px);
  height: calc(100% - 2px);
  //border: 1px solid #DCDFE6 !important;
  border-radius: 4px;
  overflow: hidden;

  ::v-deep .CodeMirror {
    font-size: 13px;
    font-family: "微软雅黑" !important;

    .CodeMirror-lines {
      margin-left: 10px;
    }

    height: 100%;

    .CodeMirror-placeholder-dark {
      color: #B5B5B5 !important;

      margin-top: -2px;
    }

    .CodeMirror-cursor {
      height: 20px !important;
      margin-top: 5px;
    }

    .cm-variable,
    .cm-string,
    .cm-number {
      color: #606266;
      margin-top: -2px;
    }

    .widget {
      display: inline-block;
      padding: 2px 4px;
      border-radius: 2px;
      background-color: rgba(3, 110, 255, 0.1);
      color: #036EFF;
    }
  }

  ::v-deep .CodeMirror-linenumber {
    color: #909399 !important;
  }

  ::v-deep .CodeMirror-gutters {
    background: #ebeef5 !important;
    border-right: 0;
  }

  ::v-deep .CodeMirror-placeholder-dark {
    color: #C0C4CC !important;
  }

  ::v-deep .CodeMirror-selected {
    background: #C0C4CC !important;
  }

  ::v-deep .CodeMirror-scroll {
    overflow-y: hidden;
    overflow-x: auto;
  }

  ::v-deep .cm-bracket {
    color: blue !important;
  }
}
</style>

<style lang="scss">
.CodeMirror-hints {
  z-index: 9999;
  font-size: 13px;
}

.CodeMirror-hint {
  padding: 4px;
}
</style>
